﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;

namespace PdfLibrary
{
    public class PdfGraphicsState
    {
        public TransformationMatrix CTM = new TransformationMatrix();

        public PdfGraphicsState Clone()
        {
            return new PdfGraphicsState() { CTM = CTM.Clone() };
        }
    }

    public class PdfTextObject
    {
        public TransformationMatrix TextMatrix = new TransformationMatrix();
        public TransformationMatrix TextLineMatrix = new TransformationMatrix();
        public double Leading = 0;
        public double JobbraBalraMozgatas = 0;
    }

    public abstract class PdfGraphicsEnvironment
    {
        public double PageWidth;
        public double PageHeight;

        public string FontName;
        public double FontSize = 10;
        public IPdfFont Font;
        public IPdfContentResources Resources;

        public Stack<PdfGraphicsState> GraphicsStateStack = new Stack<PdfGraphicsState>();
        public PdfGraphicsState CurrentGraphicsState = new PdfGraphicsState();
        public PdfTextObject CurrentTextObject = null;

        public abstract void DrawString(string text);
        public abstract void DrawImage(PdfStream imageStream);
    }

    public interface IPdfCommand
    {
        void Execute(PdfGraphicsEnvironment environment);
    }

    public class PdfCommandUnknown : IPdfCommand
    {
        public string Code;
        public IPdfObject[] Operands;

        public PdfCommandUnknown(params IPdfObject[] operands)
        {
            this.Operands = operands;
        }

        public void Execute(PdfGraphicsEnvironment environment)
        {
        }
    }

    public class PdfCommandBeginText : IPdfCommand
    {
        public void Execute(PdfGraphicsEnvironment environment)
        {
            environment.CurrentTextObject = new PdfTextObject();
        }
    }

    public class PdfCommandEndText : IPdfCommand
    {
        public void Execute(PdfGraphicsEnvironment environment)
        {
            environment.CurrentTextObject = null;
        }
    }

    public class PdfCommandSetStrokingColourSpace : IPdfCommand
    {
        public PdfName ColourSpace;

        public PdfCommandSetStrokingColourSpace(PdfName colourSpace)
        {
            this.ColourSpace = colourSpace;
        }

        public void Execute(PdfGraphicsEnvironment environment)
        {
        }
    }

    public class PdfCommandSetNonStrokingColourSpace : IPdfCommand
    {
        public PdfName ColourSpace;

        public PdfCommandSetNonStrokingColourSpace(PdfName colourSpace)
        {
            this.ColourSpace = colourSpace;
        }

        public void Execute(PdfGraphicsEnvironment environment)
        {
        }
    }

    public class PdfCommandSetNonStrokingColour : IPdfCommand
    {
        public IPdfObject[] Operands;

        public PdfCommandSetNonStrokingColour(params IPdfObject[] operands)
        {
            this.Operands = operands;
        }

        public void Execute(PdfGraphicsEnvironment environment)
        {
        }
    }

    public class PdfCommandSetStrokingColour : IPdfCommand
    {
        public IPdfObject[] Operands;

        public PdfCommandSetStrokingColour(params IPdfObject[] operands)
        {
            this.Operands = operands;
        }

        public void Execute(PdfGraphicsEnvironment environment)
        {
        }
    }

    public class PdfCommandSetNextLineStart : IPdfCommand
    {
        public double tx;
        public double ty;

        public PdfCommandSetNextLineStart(PdfNumber tx, PdfNumber ty)
        {
            this.tx = tx.value;
            this.ty = ty.value;
        }

        public virtual void Execute(PdfGraphicsEnvironment environment)
        {
            var m = new TransformationMatrix(1, 0, 0, 1, tx, ty);
            var to = environment.CurrentTextObject;
            to.TextMatrix = to.TextLineMatrix = m * to.TextLineMatrix;
        }
    }

    public class PdfCommandSetNextLineStartAndLeading : PdfCommandSetNextLineStart
    {
        public PdfCommandSetNextLineStartAndLeading(PdfNumber tx, PdfNumber ty):base(tx,ty)
        {
        }

        public override void Execute(PdfGraphicsEnvironment environment)
        {
            base.Execute(environment);
            environment.CurrentTextObject.Leading = -ty;
        }
    }

    public class PdfCommandSetNextLineStartWithLeading: IPdfCommand
    {
        public void Execute(PdfGraphicsEnvironment environment)
        {
            var m = new TransformationMatrix(1, 0, 0, 1, 0, -environment.CurrentTextObject.Leading);
            var to = environment.CurrentTextObject;
            to.TextMatrix = to.TextLineMatrix = m * to.TextLineMatrix;
        }
    }

    public class PdfCommandSetFlatnessTolarence : IPdfCommand
    {
        public PdfNumber Value;

        public PdfCommandSetFlatnessTolarence(PdfNumber value)
        {
            this.Value = value;
        }

        public void Execute(PdfGraphicsEnvironment environment)
        {
        }
    }

    public class PdfCommandShowTextArray : IPdfCommand
    {
        public PdfArray Content;

        public PdfCommandShowTextArray(PdfArray content)
        {
            this.Content = content;
        }

        public void Execute(PdfGraphicsEnvironment environment)
        {
            environment.CurrentTextObject.JobbraBalraMozgatas = 0;
            //environment.DrawString(String.Join("", this.Content.OfType<PdfString>().Select(s => s.Value)));
            foreach (var item in this.Content)
                if (item is PdfString)
                    environment.DrawString(((PdfString)item).Value);
                else if (item is PdfNumber)
                    environment.CurrentTextObject.JobbraBalraMozgatas -= ((PdfNumber) item).value / 1000;
        }
    }

    public class PdfCommandShowText : IPdfCommand
    {
        public string Content;

        public PdfCommandShowText(PdfString content)
        {
            this.Content = content.Value;
        }

        public void Execute(PdfGraphicsEnvironment environment)
        {
            environment.CurrentTextObject.JobbraBalraMozgatas = 0;
            environment.DrawString(Content);
        }
    }

    public class PdfCommandSetFontSize : IPdfCommand
    {
        public string PdfFontName;
        public double FontSize;

        public PdfCommandSetFontSize(PdfName font, PdfNumber size)
        {
            this.PdfFontName = font.Value;
            this.FontSize = size.value;
        }

        public void Execute(PdfGraphicsEnvironment environment)
        {
            var item = environment.Resources.Font[this.PdfFontName];
            var font = item as IPdfFont;
            environment.Font = font;
            environment.FontName = font.BaseFont;
            environment.FontSize = FontSize;
        }
    }

    public class PdfCommandSaveGraphicsState : IPdfCommand
    {
        public void Execute(PdfGraphicsEnvironment environment)
        {
            environment.GraphicsStateStack.Push(environment.CurrentGraphicsState.Clone());
        }
    }

    public class PdfCommandRestoreGraphicsState : IPdfCommand
    {
        public void Execute(PdfGraphicsEnvironment environment)
        {
            environment.CurrentGraphicsState = environment.GraphicsStateStack.Pop();
        }
    }

    public class PdfCommandCTMConcatenate : IPdfCommand
    {
        public TransformationMatrix Matrix;

        public PdfCommandCTMConcatenate(params IPdfObject[] operands)
        {
            this.Matrix = new TransformationMatrix(operands.Select(o => ((PdfNumber) o).value).ToArray());
        }

        public void Execute(PdfGraphicsEnvironment environment)
        {
            environment.CurrentGraphicsState.CTM = Matrix * environment.CurrentGraphicsState.CTM;
        }
    }

    public class PdfCommandSetTextMatrix : IPdfCommand
    {
        public TransformationMatrix Matrix;

        public PdfCommandSetTextMatrix(params IPdfObject[] operands)
        {
            this.Matrix = new TransformationMatrix(operands.Select(o => ((PdfNumber)o).value).ToArray());
        }

        public void Execute(PdfGraphicsEnvironment environment)
        {
            environment.CurrentTextObject.TextLineMatrix = environment.CurrentTextObject.TextMatrix = Matrix;
        }
    }

    public class PdfCommandPaintXObject: IPdfCommand
    {
        public string ObjectName;

        public PdfCommandPaintXObject(PdfName objectName)
        {
            this.ObjectName = objectName.Value;
        }

        public void Execute(PdfGraphicsEnvironment environment)
        {
            var obj = (PdfStream) environment.Resources.XObject[ObjectName];
            
            environment.DrawImage(obj);
        }
    }
}
